import os
from check_modules import check_and_install_modules

# Путь к текущему файлу, который мы проверяем на импорты
file_path = __file__

# Проверка и установка недостающих модулей
check_and_install_modules(file_path)

# После установки делаем импорт нужных пакетов
import requests
import tkinter as tk
from tkinter import messagebox, filedialog, font, simpledialog
from tkinter import ttk
import threading
import webbrowser
from concurrent.futures import ThreadPoolExecutor, as_completed
import queue
import time
from datetime import datetime
from tkinterdnd2 import TkinterDnD, DND_FILES
import re

# Путь к файлу с токенами
TOKENS_FILE_PATH = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\токены для автопостинга.txt"
# Путь к файлу для неуспешных публикаций
FAILED_TOKENS_FILE_PATH = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\Токены неуспешных публикаций.txt"
# Путь к файлу для хранения последнего выложенного поста
LAST_POST_FILE_PATH = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\last_post.txt"

# Словарь для хранения информации о пользователях
user_info_dict = {}

# Настройки для более приятного дизайна
PRIMARY_COLOR = "#4CAF50"       # Основной цвет (например, зелёный)
SECONDARY_COLOR = "#f0f0f0"       # Цвет фона приложения
BUTTON_COLOR = "#2196F3"          # Цвет кнопок (синий)
FONT_STYLE = ("Arial", 12)
HEADER_FONT = ("Arial", 14, "bold")
LINK_COLOR = "#0b7dda"            # Цвет ссылок

# Очередь для сообщений из потоков
log_queue = queue.Queue()

# Флаг для остановки процесса публикации
stop_flag = threading.Event()

# Функция для сортировки с учётом чисел в названиях файлов
def natural_key(filename):
    basename = os.path.basename(filename)
    return [text for text in re.split(r'(\d+)', basename)]

def get_user_info(token):
    url = 'https://api.vk.com/method/users.get'
    params = {
        'access_token': token,
        'v': '5.131'
    }
    response = requests.get(url, params=params)
    data = response.json()
    if 'response' in data:
        user_info = data['response'][0]
        user_info_dict[token] = f"{user_info['first_name']} {user_info['last_name']}"
        return user_info['id'], user_info_dict[token]
    elif 'error' in data and data['error'].get('error_code') == 5:
        raise Exception(f"Ошибка авторизации: {data['error']['error_msg']}")
    else:
        raise Exception("Ошибка получения информации о пользователе: " + str(data.get('error', {})))

def upload_photo(token, photo_path):
    url = 'https://api.vk.com/method/photos.getWallUploadServer'
    params = {
        'access_token': token,
        'v': '5.131'
    }
    response = requests.post(url, params=params)
    response.raise_for_status()
    upload_info = response.json()

    if 'response' not in upload_info:
        raise Exception("Ошибка получения сервера для загрузки фото: " + str(upload_info.get('error', {})))

    upload_url = upload_info['response']['upload_url']
    
    with open(photo_path, 'rb') as photo_file:
        files = {'photo': photo_file}
        upload_response = requests.post(upload_url, files=files)
        upload_response.raise_for_status()
        upload_data = upload_response.json()

    if 'photo' not in upload_data:
        raise Exception("Ошибка загрузки фото: " + str(upload_data))

    return upload_data

def save_photo(token, upload_data, user_id):
    url = 'https://api.vk.com/method/photos.saveWallPhoto'
    params = {
        'access_token': token,
        'user_id': user_id,
        'server': upload_data['server'],
        'photo': upload_data['photo'],
        'hash': upload_data['hash'],
        'v': '5.131'
    }
    response = requests.post(url, params=params)
    response.raise_for_status()
    data = response.json()
    if 'response' not in data:
        raise Exception("Ошибка сохранения фото: " + str(data.get('error', {})))
    return data

def upload_video(token, video_path):
    url = 'https://api.vk.com/method/video.save'
    params = {
        'access_token': token,
        'v': '5.131',
        'name': os.path.basename(video_path)
    }
    response = requests.post(url, params=params)
    response.raise_for_status()
    video_info = response.json()

    if 'response' not in video_info:
        raise Exception("Ошибка получения сервера для загрузки видео: " + str(video_info.get('error', {})))

    upload_url = video_info['response']['upload_url']
    
    with open(video_path, 'rb') as video_file:
        files = {'video_file': video_file}
        upload_response = requests.post(upload_url, files=files)
        upload_response.raise_for_status()
        upload_data = upload_response.json()

    if 'video_id' not in upload_data:
        raise Exception("Ошибка загрузки видео: " + str(upload_data))

    return upload_data

def post_to_vk(token, user_id, message, attachments):
    url = 'https://api.vk.com/method/wall.post'
    params = {
        'access_token': token,
        'owner_id': user_id,
        'message': message,
        'attachments': attachments,
        'v': '5.131'
    }
    response = requests.post(url, params=params)
    response.raise_for_status()
    data = response.json()
    if 'error' in data:
        raise Exception("Ошибка публикации поста: " + data['error']['error_msg'])
    return data

def submit_post():
    stop_flag.clear()  
    post_button['state'] = tk.DISABLED
    stop_button['state'] = tk.NORMAL
    cycle_count = cycle_var.get()
    thread = threading.Thread(target=post_to_accounts_concurrently, args=(cycle_count,))
    thread.start()

def stop_posting():
    stop_flag.set()
    log_queue.put("Публикация остановлена пользователем.\n")
    stop_button['state'] = tk.DISABLED
    post_button['state'] = tk.NORMAL

def post_to_account(token, user_id, message, photos, videos):
    if stop_flag.is_set():
        return ("Публикация остановлена пользователем.", False, None)
    max_attempts = 10
    attempt = 0
    user_name = ""
    while attempt < max_attempts:
        if stop_flag.is_set():
            return ("Публикация остановлена пользователем.", False, None)
        attempt += 1
        try:
            user_id, user_name = get_user_info(token)
            log_queue.put(f"Публикация на аккаунте: {user_name}... Попытка {attempt}\n")
            attachments = []

            for photo in photos:
                if stop_flag.is_set():
                    return ("Публикация остановлена пользователем.", False, None)
                log_queue.put(f"Загрузка фото: {os.path.basename(photo)} на аккаунт {user_name}...\n")
                upload_data = upload_photo(token, photo)
                saved_photo = save_photo(token, upload_data, user_id)
                if 'response' in saved_photo:
                    attachments.append(f"photo{saved_photo['response'][0]['owner_id']}_{saved_photo['response'][0]['id']}")
                else:
                    raise Exception("Не удалось сохранить фото.")

            for video in videos:
                if stop_flag.is_set():
                    return ("Публикация остановлена пользователем.", False, None)
                log_queue.put(f"Загрузка видео: {os.path.basename(video)} на аккаунт {user_name}...\n")
                upload_data = upload_video(token, video)
                if 'video_id' in upload_data:
                    attachments.append(f"video{user_id}_{upload_data['video_id']}")
                else:
                    raise Exception("Не удалось загрузить видео.")

            result = post_to_vk(token, user_id, message, ','.join(attachments))
            post_id = result['response']['post_id']
            link = f"https://vk.com/wall{user_id}_{post_id}"
            return (f"Пост успешно опубликован на аккаунте: {user_name}", link, user_id)
        except Exception as e:
            if attempt >= max_attempts:
                user_name = user_info_dict.get(token, token)
                error_message = f"Ошибка на аккаунте: {user_name}: {str(e)} после {max_attempts} попыток\n"
                return error_message, False, token
            else:
                log_queue.put(f"Попытка {attempt} не удалась на аккаунте {user_name}. Ошибка: {e}\n")
                time.sleep(1)

def post_to_accounts_concurrently(cycle_count):
    global successful_posts_list, errors_list
    successful_posts_list = []
    errors_list = []
    failed_tokens_set = set()  
    accounts_posted_set = set()  

    total_tokens = len(tokens)
    total_accounts = len(posting_schedule) * (cycle_count if cycle_count != 0 else 1) * len(tokens)
    progress_bar['maximum'] = total_accounts
    progress_bar['value'] = 0

    output_text.delete("1.0", tk.END)
    successful_text.delete("1.0", tk.END)
    failed_text.delete("1.0", tk.END)

    successful_posts = 0
    failed_posts = 0

    for entry in posting_schedule:
        entry['photos'] = sorted(entry['photos'], key=natural_key)
        entry['videos'] = sorted(entry['videos'], key=natural_key)

    log_queue.put("Файлы отсортированы.\n")

    cycles_done = 0
    while (cycle_count == 0 or cycles_done < cycle_count) and not stop_flag.is_set():
        log_queue.put(f"Начало цикла публикации {cycles_done + 1}\n")
        for entry in posting_schedule:
            if stop_flag.is_set():
                break
            message = entry['text']
            photos = entry['photos']
            videos = entry['videos']
            with ThreadPoolExecutor(max_workers=10) as executor:
                valid_tokens = [token for token in tokens if token not in failed_tokens_set]
                future_to_token = {
                    executor.submit(post_to_account, token, None, message, photos, videos): token 
                    for token in valid_tokens
                }
                for future in as_completed(future_to_token):
                    if stop_flag.is_set():
                        break
                    token = future_to_token[future]
                    try:
                        result = future.result()
                        if len(result) == 3:
                            if result[1] == False:
                                failed_posts += 1
                                update_failed_posts_threadsafe(result[0])
                                failed_tokens_set.add(result[2])
                            else:
                                successful_posts += 1
                                accounts_posted_set.add(result[2])
                                update_successful_posts_threadsafe(result[0], result[1])
                        else:
                            failed_posts += 1
                            update_failed_posts_threadsafe("Неизвестная ошибка.\n")
                            failed_tokens_set.add(token)
                        progress_bar['value'] += 1
                        update_progress_label_threadsafe(successful_posts, failed_posts, total_accounts)
                    except Exception as exc:
                        user_name = user_info_dict.get(token, token)
                        error_message = f"Ошибка на аккаунте: {user_name}: {str(exc)}\n"
                        failed_posts += 1
                        update_failed_posts_threadsafe(error_message)
                        failed_tokens_set.add(token)
                        progress_bar['value'] += 1
                        update_progress_label_threadsafe(successful_posts, failed_posts, total_accounts)
        cycles_done += 1
        log_queue.put(f"Цикл публикации {cycles_done} завершен.\n")

    if failed_tokens_set:
        write_failed_tokens(failed_tokens_set)

    log_queue.put("Публикация завершена.\n")
    stop_button['state'] = tk.DISABLED
    post_button['state'] = tk.NORMAL

def write_failed_tokens(failed_tokens_set):
    separator = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + " ------------------------\n"
    try:
        with open(FAILED_TOKENS_FILE_PATH, 'a', encoding='utf-8') as file:
            file.write(separator)
            for token in failed_tokens_set:
                file.write(token + '\n')
        log_queue.put(f"Токены неуспешных публикаций записаны в файл {FAILED_TOKENS_FILE_PATH}\n")
    except Exception as e:
        log_queue.put(f"Ошибка при записи токенов неуспешных публикаций: {e}\n")

def update_progress_label_threadsafe(success_count, failed_count, total_accounts):
    remaining_count = total_accounts - (success_count + failed_count)
    progress_info = {
        'success_count': success_count,
        'failed_count': failed_count,
        'remaining_count': remaining_count
    }
    log_queue.put(('update_progress', progress_info))

def update_successful_posts_threadsafe(message, link):
    log_queue.put(('update_success', message, link))

def update_failed_posts_threadsafe(message):
    log_queue.put(('update_failed', message))

def process_queue():
    try:
        while True:
            item = log_queue.get_nowait()
            if isinstance(item, str):
                output_text.insert(tk.END, item)
                if auto_scroll_var.get():
                    output_text.see(tk.END)
            elif isinstance(item, tuple):
                if item[0] == 'update_progress':
                    info = item[1]
                    progress_label.config(text=f"Осталось постов: {info['remaining_count']}")
                    successful_posts_label.config(text=f"Успешные публикации: {info['success_count']}")
                    failed_posts_label.config(text=f"Неуспешные публикации: {info['failed_count']}")
                elif item[0] == 'update_success':
                    message, link = item[1], item[2]
                    update_successful_posts(message, link)
                elif item[0] == 'update_failed':
                    message = item[1]
                    update_failed_posts(message)
    except queue.Empty:
        pass
    root.after(100, process_queue)

def update_successful_posts(message, link):
    successful_text.insert(tk.END, message + " ")
    successful_text.insert(tk.END, link, "hyperlink")
    successful_text.tag_bind("hyperlink", "<Button-1>", lambda e, url=link: webbrowser.open(url))
    successful_text.insert(tk.END, "\n")
    if auto_scroll_var.get():
        successful_text.see(tk.END)

def update_failed_posts(message):
    failed_text.insert(tk.END, message + "\n")
    if auto_scroll_var.get():
        failed_text.see(tk.END)

def load_tokens():
    global tokens
    if os.path.exists(TOKENS_FILE_PATH):
        with open(TOKENS_FILE_PATH, 'r') as file:
            tokens = [token.strip() for token in file.readlines() if token.strip()]
        tokens_label.config(text=f"Токенов: {len(tokens)}")
        if tokens:
            enable_buttons()
        else:
            messagebox.showwarning("Предупреждение", "Файл не содержит токенов.")
    else:
        messagebox.showerror("Ошибка", f"Файл с токенами не найден по пути: {TOKENS_FILE_PATH}")

def enable_buttons():
    post_button['state'] = tk.NORMAL
    add_schedule_button['state'] = tk.NORMAL
    remove_schedule_button['state'] = tk.NORMAL

def create_context_menu(widget):
    context_menu = tk.Menu(widget, tearoff=0, font=FONT_STYLE, bg="white", fg="black")
    context_menu.add_command(label="Вырезать", command=lambda: widget.event_generate("<<Cut>>"))
    context_menu.add_command(label="Копировать", command=lambda: widget.event_generate("<<Copy>>"))
    context_menu.add_command(label="Вставить", command=lambda: widget.event_generate("<<Paste>>"))
    context_menu.add_separator()
    context_menu.add_command(label="Выбрать все", command=lambda: widget.event_generate("<<SelectAll>>"))
    def toggle_auto_scroll():
        auto_scroll_var.set(not auto_scroll_var.get())
    if auto_scroll_var.get():
        auto_scroll_label = "Отключить автопрокрутку"
    else:
        auto_scroll_label = "Включить автопрокрутку"
    context_menu.add_separator()
    context_menu.add_command(label=auto_scroll_label, command=toggle_auto_scroll)
    return context_menu

def show_context_menu(event, widget):
    context_menu = create_context_menu(widget)
    context_menu.tk_popup(event.x_root, event.y_root)

def add_posting_set():
    add_window = tk.Toplevel(root)
    add_window.title("Добавить набор публикации")
    add_window.geometry("700x500")
    add_window.configure(bg=SECONDARY_COLOR)
    add_window.attributes("-topmost", True)

    title_label = ttk.Label(add_window, text="Новый набор публикации", font=HEADER_FONT)
    title_label.grid(row=0, column=0, columnspan=3, pady=(10, 5), padx=10, sticky="w")

    files_label_text = ttk.Label(add_window, text="Файлы (Фото и Видео):")
    files_label_text.grid(row=1, column=0, padx=10, pady=5, sticky="w")

    files_listbox = tk.Listbox(add_window, selectmode=tk.MULTIPLE, width=40, height=10)
    files_listbox.grid(row=2, column=0, padx=10, pady=5, sticky="w")

    files_buttons_frame = ttk.Frame(add_window)
    files_buttons_frame.grid(row=2, column=1, padx=5, pady=5, sticky="w")

    posting_schedule_files = []

    def add_files_drop_event(event, file_list, listbox):
        files = add_window.tk.splitlist(event.data)
        valid_files = [f for f in files 
                       if f.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.mp4', '.avi', '.mov', '.mkv'))]
        for file in valid_files:
            if file not in file_list:
                file_list.append(file)
                listbox.insert(tk.END, os.path.basename(file))
        log_queue.put(f"Добавлено файлов через DnD: {[os.path.basename(file) for file in valid_files]}\n")

    files_listbox.drop_target_register(DND_FILES)
    files_listbox.dnd_bind('<<Drop>>', lambda event: add_files_drop_event(event, posting_schedule_files, files_listbox))

    def add_files():
        files = filedialog.askopenfilenames(
            title="Выберите фото и/или видео",
            filetypes=[("Image and Video files", "*.jpg;*.jpeg;*.png;*.gif;*.mp4;*.avi;*.mov;*.mkv")]
        )
        for file in files:
            if file not in posting_schedule_files:
                posting_schedule_files.append(file)
                files_listbox.insert(tk.END, os.path.basename(file))
        log_queue.put(f"Добавлено файлов через диалог: {[os.path.basename(file) for file in files]}\n")

    add_files_button = ttk.Button(files_buttons_frame, text="+", width=3, command=add_files)
    add_files_button.pack(side=tk.LEFT, padx=(0, 2))

    def remove_selected_files():
        selected_indices = list(files_listbox.curselection())
        selected_indices.sort(reverse=True)
        for index in selected_indices:
            files_listbox.delete(index)
            del posting_schedule_files[index]
        log_queue.put("Удалены выбранные файлы.\n")

    remove_files_button = ttk.Button(files_buttons_frame, text="-", width=3, command=remove_selected_files)
    remove_files_button.pack(side=tk.LEFT)

    text_label = ttk.Label(add_window, text="Текст сообщения:")
    text_label.grid(row=3, column=0, padx=10, pady=(10, 5), sticky="w")

    message_entry = tk.Text(add_window, height=5, width=50, font=FONT_STYLE, wrap="word")
    message_entry.grid(row=4, column=0, columnspan=2, padx=10, pady=5, sticky="w")

    message_entry.bind("<Button-3>", lambda event: show_context_menu(event, message_entry))

    def paste_message_in_child(entry_widget):
        try:
            clipboard_text = add_window.clipboard_get()
            entry_widget.delete("1.0", tk.END)
            entry_widget.insert(tk.END, clipboard_text)
        except tk.TclError:
            messagebox.showerror("Ошибка", 
                                 "Буфер обмена пуст или содержит неподдерживаемые данные.", 
                                 parent=add_window)

    paste_button_child = ttk.Button(add_window, text="Вставить", 
                                    command=lambda: paste_message_in_child(message_entry))
    paste_button_child.grid(row=4, column=2, padx=5, pady=5, sticky="w")

    message_scrollbar = ttk.Scrollbar(add_window, orient="vertical", command=message_entry.yview)
    message_scrollbar.grid(row=4, column=3, sticky='nsw', padx=(0,10), pady=5)
    message_entry.config(yscrollcommand=message_scrollbar.set)

    buttons_frame = ttk.Frame(add_window)
    buttons_frame.grid(row=5, column=0, columnspan=3, pady=10)

    def save_entry():
        text = message_entry.get("1.0", tk.END).strip()
        files = posting_schedule_files.copy()
        if not files:
            messagebox.showwarning("Предупреждение", 
                                   "Добавьте хотя бы один файл (фото или видео).", 
                                   parent=add_window)
            return
        if not text:
            if not messagebox.askyesno("Подтверждение", 
                                       "Сообщение пустое. Продолжить?", 
                                       parent=add_window):
                return
        photos = [f for f in files if f.lower().endswith(('.jpg', '.jpeg', '.png', '.gif'))]
        videos = [f for f in files if f.lower().endswith(('.mp4', '.avi', '.mov', '.mkv'))]
        posting_schedule.append({'text': text, 'photos': photos, 'videos': videos})
        update_posting_schedule_table()
        message_entry.delete("1.0", tk.END)
        files_listbox.delete(0, tk.END)
        posting_schedule_files.clear()
        log_queue.put("Новый набор публикации сохранен.\n")

    save_button = ttk.Button(buttons_frame, text="Сохранить", command=save_entry)
    save_button.pack(side=tk.LEFT, padx=10)

    cancel_button = ttk.Button(buttons_frame, text="Отмена", command=add_window.destroy)
    cancel_button.pack(side=tk.LEFT, padx=10)

def remove_posting_set():
    selected_items = posting_schedule_table.selection()
    if not selected_items:
        messagebox.showwarning("Предупреждение", "Выберите запись для удаления.")
        return
    try:
        indices = [int(item) for item in selected_items]
        log_queue.put(f"Выбранные индексы для удаления: {indices}\n")
        
        for index in sorted(indices, reverse=True):
            if 0 <= index < len(posting_schedule):
                del posting_schedule[index]
                log_queue.put(f"Удалена публикация №{index + 1}\n")
            else:
                log_queue.put(f"Ошибка: индекс {index} вне диапазона.\n")
        
        update_posting_schedule_table()
    except ValueError:
        messagebox.showerror("Ошибка", "Не удалось определить индексы выбранных записей.")
    except Exception as e:
        messagebox.showerror("Ошибка", f"Произошла ошибка при удалении: {e}")

def update_posting_schedule_table():
    posting_schedule_table.delete(*posting_schedule_table.get_children())
    for idx, entry in enumerate(posting_schedule, start=1):
        photos = f"{len(entry['photos'])} фото" if entry['photos'] else "0 фото"
        videos = f"{len(entry['videos'])} видео" if entry['videos'] else "0 видео"
        text_preview = (entry['text'][:30] + '...') if len(entry['text']) > 30 else entry['text']
        posting_schedule_table.insert("", "end", iid=idx-1, values=(idx, photos, videos, text_preview))

def create_posting_schedule_table():
    table_frame = ttk.Frame(content_frame)
    return table_frame

def show_table_context_menu(event, table):
    context_menu = tk.Menu(table, tearoff=0)
    context_menu.add_command(label="Выбрать все", command=lambda: select_all_table(table))
    context_menu.tk_popup(event.x_root, event.y_root)

def select_all_table(table):
    table.selection_set(table.get_children())
    return 'break'

def load_last_post():
    if os.path.exists(LAST_POST_FILE_PATH):
        try:
            with open(LAST_POST_FILE_PATH, 'r', encoding='utf-8') as file:
                value = file.read().strip()
                last_post_var.set(value)
        except Exception as e:
            log_queue.put(f"Ошибка при загрузке последнего выложенного поста: {e}\n")
    else:
        last_post_var.set("")

def save_last_post(*args):
    value = last_post_var.get()
    try:
        with open(LAST_POST_FILE_PATH, 'w', encoding='utf-8') as file:
            file.write(value)
    except Exception as e:
        log_queue.put(f"Ошибка при сохранении последнего выложенного поста: {e}\n")

# Инициализация переменных
tokens = []
successful_posts_list = []
errors_list = []
posting_schedule = []

# Создаём главное окно с поддержкой DnD
root = TkinterDnD.Tk()
root.title("Автопостинг ВКонтакте")

# Устанавливаем тему оформления (например, "clam")
style = ttk.Style()
style.theme_use("clam")

# Дополнительная настройка стилей
style.configure(".", font=FONT_STYLE)
style.configure("TButton",
                foreground="white",
                background=BUTTON_COLOR,
                padding=6,
                borderwidth=0)
style.map("TButton",
          foreground=[("disabled", "#cccccc")],
          background=[("active", "#1976D2"), ("disabled", "#90CAF9")])
style.configure("TLabel", background=SECONDARY_COLOR, foreground="black")
style.configure("TFrame", background=SECONDARY_COLOR)
style.configure("TLabelFrame", background=SECONDARY_COLOR, font=HEADER_FONT)
style.configure("Vertical.TScrollbar", troughcolor=SECONDARY_COLOR)

# Настраиваем размеры окна
window_width = 1200
window_height = 1000
root.geometry(f"{window_width}x{window_height}")

screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
position_top = int(screen_height / 2 - window_height / 2)
position_right = int(screen_width / 2 - window_width / 2)
root.geometry(f"{window_width}x{window_height}+{position_right}+{position_top}")
root.configure(bg=SECONDARY_COLOR)

# Переменная для управления автопрокруткой
auto_scroll_var = tk.BooleanVar(value=True)

content_frame = ttk.Frame(root)
content_frame.pack(padx=20, pady=20, fill="both", expand=True)

### Прогресс-бар и статистика (верхняя часть) ###
progress_frame = ttk.Frame(content_frame)
progress_frame.pack(pady=10, fill="x")

progress_bar = ttk.Progressbar(progress_frame, length=800, mode='determinate')
progress_bar.pack(pady=(0, 5), fill="x", expand=True)

progress_label = ttk.Label(progress_frame, text="Осталось постов: 0")
progress_label.pack()

info_frame = ttk.Frame(content_frame)
info_frame.pack(pady=10, fill="x")

successful_posts_label = ttk.Label(info_frame, text="Успешные публикации: 0", foreground=PRIMARY_COLOR)
successful_posts_label.pack(side=tk.LEFT, padx=(0, 20))

failed_posts_label = ttk.Label(info_frame, text="Неуспешные публикации: 0", foreground="red")
failed_posts_label.pack(side=tk.LEFT)

### Поле "Последний выложенный пост" ###
last_post_frame = ttk.Frame(content_frame)
last_post_frame.pack(pady=10, fill="x")

last_post_label = ttk.Label(last_post_frame, text="Последний выложенный пост:")
last_post_label.pack(side=tk.LEFT, padx=(0, 10))

last_post_var = tk.StringVar()
last_post_entry = ttk.Entry(last_post_frame, textvariable=last_post_var, width=30)
last_post_entry.pack(side=tk.LEFT)
last_post_entry.config(validate="key", validatecommand=(root.register(lambda P: len(P) <= 5), '%P'))

last_post_var.trace_add('write', save_last_post)
load_last_post()

### Кнопки "Указать аккаунты" и др. ###
account_frame = ttk.Frame(content_frame)
account_frame.pack(pady=10, fill="x")

account_button = ttk.Button(account_frame, text="Указать аккаунты", command=load_tokens)
account_button.pack(side=tk.LEFT, padx=(0, 10))

tokens_label = ttk.Label(account_frame, text="Токенов: 0")
tokens_label.pack(side=tk.LEFT)

# Отключаем главный DnD (пустая функция)
root.drop_target_register(DND_FILES)
root.dnd_bind('<<Drop>>', lambda event: None)

### Основные кнопки управления ###
controls_frame = ttk.Frame(content_frame)
controls_frame.pack(pady=10)

post_button = ttk.Button(controls_frame, text="Выложить пост", command=submit_post, state=tk.DISABLED)
post_button.pack(side=tk.LEFT, padx=(0, 10))

stop_button = ttk.Button(controls_frame, text="Остановить", command=stop_posting, state=tk.DISABLED)
stop_button.pack(side=tk.LEFT, padx=(0, 10))

add_schedule_button = ttk.Button(controls_frame, text="Добавить набор публикаций", command=add_posting_set, state=tk.DISABLED)
add_schedule_button.pack(side=tk.LEFT, padx=(0, 10))

remove_schedule_button = ttk.Button(controls_frame, text="Удалить выбранные", command=remove_posting_set, state=tk.DISABLED)
remove_schedule_button.pack(side=tk.LEFT, padx=(0, 10))

### Область расписания и логов ###
tables_frame = ttk.Frame(content_frame)
tables_frame.pack(pady=10, fill="both", expand=True)

# Левая часть — таблица расписания публикаций
left_table_frame = ttk.Frame(tables_frame)
left_table_frame.pack(side=tk.LEFT, fill="both", expand=True, padx=(0, 10))

posting_schedule_table = ttk.Treeview(left_table_frame, 
                                      columns=("№", "Фото", "Видео", "Текст"), 
                                      show='headings', 
                                      selectmode='extended', 
                                      height=10)
for col in ("№", "Фото", "Видео", "Текст"):
    posting_schedule_table.heading(col, text=col)
    if col == "Текст":
        posting_schedule_table.column(col, width=300)
    else:
        posting_schedule_table.column(col, width=100)
posting_schedule_table.pack(side=tk.LEFT, fill="both", expand=True)

scrollbar_table = ttk.Scrollbar(left_table_frame, orient="vertical", command=posting_schedule_table.yview)
scrollbar_table.pack(side=tk.RIGHT, fill="y")
posting_schedule_table.config(yscrollcommand=scrollbar_table.set)

posting_schedule_table.bind("<Button-3>", lambda event: show_table_context_menu(event, posting_schedule_table))
posting_schedule_table.bind("<Control-a>", lambda event: select_all_table(posting_schedule_table))
posting_schedule_table.bind("<Control-A>", lambda event: select_all_table(posting_schedule_table))

# Правая часть — логи процесса
right_log_frame = ttk.Frame(tables_frame)
right_log_frame.pack(side=tk.LEFT, fill="both", expand=True, padx=(10, 0))

output_label = ttk.Label(right_log_frame, text="Процесс публикации:")
output_label.pack(anchor="w", pady=(0, 5))

output_frame = ttk.Frame(right_log_frame)
output_frame.pack(pady=(0, 10), fill="both", expand=True)

output_text = tk.Text(output_frame, 
                      width=50, 
                      height=10, 
                      font=FONT_STYLE, 
                      wrap="word", 
                      bg="#f9f9f9", 
                      relief="solid", 
                      bd=1)
output_text.pack(side=tk.LEFT, fill="both", expand=True)

output_scrollbar = ttk.Scrollbar(output_frame, orient="vertical", command=output_text.yview)
output_scrollbar.pack(side=tk.RIGHT, fill="y")
output_text.config(yscrollcommand=output_scrollbar.set)
output_text.bind("<Button-3>", lambda event: show_context_menu(event, output_text))

### Область успешных и неуспешных публикаций ###
publishing_frame = ttk.Frame(content_frame)
publishing_frame.pack(pady=10, fill="both", expand=True)

successful_frame = ttk.LabelFrame(publishing_frame, text="Успешные публикации", padding=5)
successful_frame.pack(side=tk.LEFT, padx=10, pady=10, fill="both", expand=True)

successful_text_frame = ttk.Frame(successful_frame)
successful_text_frame.pack(fill="both", expand=True)

successful_text = tk.Text(successful_text_frame, 
                          width=40, 
                          height=10, 
                          font=FONT_STYLE, 
                          cursor="hand2", 
                          wrap="word", 
                          bg="#e8f5e9", 
                          relief="solid", 
                          bd=1)
successful_text.pack(side=tk.LEFT, fill="both", expand=True)

successful_scrollbar = ttk.Scrollbar(successful_text_frame, orient="vertical", command=successful_text.yview)
successful_scrollbar.pack(side=tk.RIGHT, fill="y")
successful_text.config(yscrollcommand=successful_scrollbar.set)
successful_text.tag_config("hyperlink", foreground=LINK_COLOR, underline=True)
successful_text.bind("<Button-3>", lambda event: show_context_menu(event, successful_text))

failed_frame = ttk.LabelFrame(publishing_frame, text="Неуспешные публикации", padding=5)
failed_frame.pack(side=tk.RIGHT, padx=10, pady=10, fill="both", expand=True)

failed_text_frame = ttk.Frame(failed_frame)
failed_text_frame.pack(fill="both", expand=True)

failed_text = tk.Text(failed_text_frame, 
                      width=40, 
                      height=10, 
                      font=FONT_STYLE, 
                      wrap="word", 
                      bg="#ffebee", 
                      relief="solid", 
                      bd=1)
failed_text.pack(side=tk.LEFT, fill="both", expand=True)

failed_scrollbar = ttk.Scrollbar(failed_text_frame, orient="vertical", command=failed_text.yview)
failed_scrollbar.pack(side=tk.RIGHT, fill="y")
failed_text.config(yscrollcommand=failed_scrollbar.set)
failed_text.bind("<Button-3>", lambda event: show_context_menu(event, failed_text))

### Количество циклов ###
cycle_frame = ttk.Frame(content_frame)
cycle_frame.pack(pady=5, fill="x")

cycle_label = ttk.Label(cycle_frame, text="Количество циклов (0 - бесконечно):")
cycle_label.pack(side=tk.LEFT, padx=(0, 5))

cycle_var = tk.IntVar(value=1)
cycle_entry = ttk.Entry(cycle_frame, textvariable=cycle_var, width=10)
cycle_entry.pack(side=tk.LEFT)

root.after(100, process_queue)
root.mainloop()
